package evl

import (
	"errors"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"path/filepath"

	"gitcode.com/deyiyangyang/bampoo/lex"
	"gitcode.com/deyiyangyang/bampoo/obj"
	"gitcode.com/deyiyangyang/bampoo/par"
)

// CreateBuiltinFunctions creates a map of builtin functions
func CreateBuiltinFunctions() map[string]*obj.BuiltinValue {
	builtins := make(map[string]*obj.BuiltinValue)
	
	// Add load function (requires evaluator)
	builtins["load"] = &obj.BuiltinValue{
		Name: "load",
		RequiresEvaluator: true,
		EvaluatorAwareFn: builtinLoadWithEvaluator,
	}
	
	// Add newline function
	builtins["newline"] = &obj.BuiltinValue{
		Name: "newline",
		Fn:   builtinNewline,
	}
	
	// Add display function
	builtins["display"] = &obj.BuiltinValue{
		Name: "display",
		Fn:   builtinDisplay,
	}
	
	// List operations
	builtins["cons"] = &obj.BuiltinValue{
		Name: "cons",
		Fn:   builtinCons,
	}
	builtins["car"] = &obj.BuiltinValue{
		Name: "car",
		Fn:   builtinCar,
	}
	builtins["cdr"] = &obj.BuiltinValue{
		Name: "cdr",
		Fn:   builtinCdr,
	}
	builtins["list"] = &obj.BuiltinValue{
		Name: "list",
		Fn:   builtinList,
	}
	builtins["null?"] = &obj.BuiltinValue{
		Name: "null?",
		Fn:   builtinNull,
	}
	builtins["pair?"] = &obj.BuiltinValue{
		Name: "pair?",
		Fn:   builtinPair,
	}
	
	// Object identity
	builtins["eq?"] = &obj.BuiltinValue{
		Name: "eq?",
		Fn:   builtinEq,
	}
	builtins["eqv?"] = &obj.BuiltinValue{
		Name: "eqv?",
		Fn:   builtinEqv,
	}
	builtins["equal?"] = &obj.BuiltinValue{
		Name: "equal?",
		Fn:   builtinEqual,
	}
	
	// Numeric functions
	builtins["numerator"] = &obj.BuiltinValue{
		Name: "numerator",
		Fn:   builtinNumerator,
	}
	builtins["denominator"] = &obj.BuiltinValue{
		Name: "denominator",
		Fn:   builtinDenominator,
	}
	builtins["exact->inexact"] = &obj.BuiltinValue{
		Name: "exact->inexact",
		Fn:   builtinExactToInexact,
	}
	builtins["inexact->exact"] = &obj.BuiltinValue{
		Name: "inexact->exact",
		Fn:   builtinInexactToExact,
	}
	builtins["floor"] = &obj.BuiltinValue{
		Name: "floor",
		Fn:   builtinFloor,
	}
	builtins["ceiling"] = &obj.BuiltinValue{
		Name: "ceiling",
		Fn:   builtinCeiling,
	}
	builtins["truncate"] = &obj.BuiltinValue{
		Name: "truncate",
		Fn:   builtinTruncate,
	}
	builtins["round"] = &obj.BuiltinValue{
		Name: "round",
		Fn:   builtinRound,
	}
	
	// Type checking functions
	builtins["number?"] = &obj.BuiltinValue{
		Name: "number?",
		Fn:   builtinNumberP,
	}
	builtins["real?"] = &obj.BuiltinValue{
		Name: "real?",
		Fn:   builtinRealP,
	}
	builtins["rational?"] = &obj.BuiltinValue{
		Name: "rational?",
		Fn:   builtinRationalP,
	}
	builtins["integer?"] = &obj.BuiltinValue{
		Name: "integer?",
		Fn:   builtinIntegerP,
	}
	builtins["symbol?"] = &obj.BuiltinValue{
		Name: "symbol?",
		Fn:   builtinSymbolP,
	}
	builtins["string?"] = &obj.BuiltinValue{
		Name: "string?",
		Fn:   builtinStringP,
	}
	builtins["boolean?"] = &obj.BuiltinValue{
		Name: "boolean?",
		Fn:   builtinBooleanP,
	}
	
	return builtins
}

// builtinNewline implements the newline function
func builtinNewline(args []obj.Value) (obj.Value, error) {
	if len(args) != 0 {
		return nil, fmt.Errorf("newline: expected 0 arguments, got %d", len(args))
	}
	fmt.Println()
	return &obj.NilValue{}, nil
}

// builtinCons implements the cons function
// (cons a b) creates a new pair with a as the car and b as the cdr
func builtinCons(args []obj.Value) (obj.Value, error) {
	if len(args) != 2 {
		return nil, fmt.Errorf("cons: expected 2 arguments, got %d", len(args))
	}
	return &obj.PairValue{Car: args[0], Cdr: args[1]}, nil
}

// builtinCar implements the car function
// (car pair) returns the car of the pair
func builtinCar(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("car: expected 1 argument, got %d", len(args))
	}
	pair, ok := args[0].(*obj.PairValue)
	if !ok {
		return nil, fmt.Errorf("car: expected pair argument, got %s", args[0].Type())
	}
	return pair.Car, nil
}

// builtinCdr implements the cdr function
// (cdr pair) returns the cdr of the pair
func builtinCdr(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("cdr: expected 1 argument, got %d", len(args))
	}
	pair, ok := args[0].(*obj.PairValue)
	if !ok {
		return nil, fmt.Errorf("cdr: expected pair argument, got %s", args[0].Type())
	}
	return pair.Cdr, nil
}

// builtinList implements the list function
// (list arg1 arg2 ...) creates a list of the arguments
func builtinList(args []obj.Value) (obj.Value, error) {
	if len(args) == 0 {
		return &obj.NilValue{}, nil
	}
	
	// Build list as cons pairs
	var result, current *obj.PairValue
	for i, arg := range args {
		pair := &obj.PairValue{Car: arg}
		if i == 0 {
			result = pair
			current = pair
		} else {
			current.Cdr = pair
			current = pair
		}
	}
	// Set the last cdr to nil
	current.Cdr = &obj.NilValue{}
	return result, nil
}

// builtinDisplay implements the display function
// (display obj) displays the object
func builtinDisplay(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("display: expected 1 argument, got %d", len(args))
	}
	fmt.Print(args[0].String())
	return &obj.NilValue{}, nil
}

// builtinNull implements the null? function
// (null? obj) returns #t if obj is the empty list, #f otherwise
func builtinNull(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("null?: expected 1 argument, got %d", len(args))
	}
	_, ok := args[0].(*obj.NilValue)
	return &obj.BooleanValue{Value: ok}, nil
}

// builtinPair implements the pair? function
// (pair? obj) returns #t if obj is a pair, #f otherwise
func builtinPair(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("pair?: expected 1 argument, got %d", len(args))
	}
	_, ok := args[0].(*obj.PairValue)
	return &obj.BooleanValue{Value: ok}, nil
}

// builtinLoadWithEvaluator implements the load function with access to the evaluator
// (load filename) loads and evaluates the Scheme expressions in the specified file
func builtinLoadWithEvaluator(evaluator interface{}, args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("load: expected 1 argument, got %d", len(args))
	}

	// Get filename as string
	filenameObj, ok := args[0].(*obj.StringValue)
	if !ok {
		return nil, fmt.Errorf("load: expected string argument, got %s", args[0].Type())
	}

	filename := filenameObj.Value

	// Resolve absolute path
	absPath, err := filepath.Abs(filename)
	if err != nil {
		return nil, fmt.Errorf("load: could not resolve path: %s", err)
	}

	// Check if file exists
	if _, err := os.Stat(absPath); os.IsNotExist(err) {
		return nil, fmt.Errorf("load: file does not exist: %s", absPath)
	}

	// Read file content
	content, err := ioutil.ReadFile(absPath)
	if err != nil {
		return nil, fmt.Errorf("load: error reading file: %s", err)
	}

	// Lex, parse, and evaluate the script
	lexer := lex.New(string(content))
	parser := par.New(lexer)
	program := parser.ParseProgram()

	// Check for parser errors
	if len(parser.Errors()) > 0 {
		errMsg := "load: parser errors:\n"
		for _, err := range parser.Errors() {
			errMsg += "  " + err + "\n"
		}
		return nil, errors.New(errMsg)
	}

	// Cast evaluator to the correct type
	// This is safe since we know the evaluator is passed from the EvalFunctionCall method
	e, ok := evaluator.(*Evaluator)
	if !ok {
		return nil, fmt.Errorf("load: invalid evaluator type")
	}

	// Evaluate the program using the current evaluator and environment
	result, err := e.Eval(program)
	if err != nil {
		return nil, fmt.Errorf("load: error evaluating file: %s", err)
	}

	// Return the result of the last expression in the file
	return result, nil
}

// builtinLoad is kept for backward compatibility but will not be used
// since we've registered load to use the evaluator-aware version
func builtinLoad(args []obj.Value) (obj.Value, error) {
	return nil, fmt.Errorf("load: evaluator not available")
}

// builtinNumberP implements the number? function
// (number? obj) returns #t if obj is a number, #f otherwise
func builtinNumberP(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("number?: expected 1 argument, got %d", len(args))
	}
	
	// Check if the value is any kind of number
	switch args[0].(type) {
	case *obj.IntegerValue, *obj.RationalValue, *obj.RealValue:
		return &obj.BooleanValue{Value: true}, nil
	default:
		return &obj.BooleanValue{Value: false}, nil
	}
}

// builtinRealP implements the real? function
// (real? obj) returns #t if obj is a real number, #f otherwise
func builtinRealP(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("real?: expected 1 argument, got %d", len(args))
	}
	
	// Check if the value is a real number
	switch args[0].(type) {
	case *obj.IntegerValue, *obj.RationalValue, *obj.RealValue:
		return &obj.BooleanValue{Value: true}, nil
	default:
		return &obj.BooleanValue{Value: false}, nil
	}
}

// builtinRationalP implements the rational? function
// (rational? obj) returns #t if obj is a rational number, #f otherwise
func builtinRationalP(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("rational?: expected 1 argument, got %d", len(args))
	}
	
	// Check if the value is a rational number
	switch args[0].(type) {
	case *obj.IntegerValue, *obj.RationalValue:
		return &obj.BooleanValue{Value: true}, nil
	default:
		return &obj.BooleanValue{Value: false}, nil
	}
}

// builtinIntegerP implements the integer? function
// (integer? obj) returns #t if obj is an integer, #f otherwise
func builtinIntegerP(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("integer?: expected 1 argument, got %d", len(args))
	}
	
	// Check if the value is an integer
	_, ok := args[0].(*obj.IntegerValue)
	return &obj.BooleanValue{Value: ok}, nil
}

// builtinSymbolP implements the symbol? function
// (symbol? obj) returns #t if obj is a symbol, #f otherwise
func builtinSymbolP(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("symbol?: expected 1 argument, got %d", len(args))
	}
	
	// Check if the value is a symbol
	_, ok := args[0].(*obj.SymbolValue)
	return &obj.BooleanValue{Value: ok}, nil
}

// builtinStringP implements the string? function
// (string? obj) returns #t if obj is a string, #f otherwise
func builtinStringP(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("string?: expected 1 argument, got %d", len(args))
	}
	
	// Check if the value is a string
	_, ok := args[0].(*obj.StringValue)
	return &obj.BooleanValue{Value: ok}, nil
}

// builtinBooleanP implements the boolean? function
// (boolean? obj) returns #t if obj is a boolean, #f otherwise
func builtinBooleanP(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("boolean?: expected 1 argument, got %d", len(args))
	}
	
	// Check if the value is a boolean
	_, ok := args[0].(*obj.BooleanValue)
	return &obj.BooleanValue{Value: ok}, nil
}
// builtinEq implements the eq? function
// (eq? obj1 obj2) returns #t if obj1 and obj2 are the same object
func builtinEq(args []obj.Value) (obj.Value, error) {
	if len(args) != 2 {
		return nil, fmt.Errorf("eq?: expected 2 arguments, got %d", len(args))
	}
	return &obj.BooleanValue{Value: args[0] == args[1]}, nil
}

// builtinEqv implements the eqv? function
// (eqv? obj1 obj2) returns #t if obj1 and obj2 are equivalent
func builtinEqv(args []obj.Value) (obj.Value, error) {
	if len(args) != 2 {
		return nil, fmt.Errorf("eqv?: expected 2 arguments, got %d", len(args))
	}
	
	// Handle nil values
	if args[0] == nil && args[1] == nil {
		return &obj.BooleanValue{Value: true}, nil
	} else if args[0] == nil || args[1] == nil {
		return &obj.BooleanValue{Value: false}, nil
	}
	
	// For symbols, use eq? (same reference)
	if _, ok1 := args[0].(*obj.SymbolValue); ok1 {
		if _, ok2 := args[1].(*obj.SymbolValue); ok2 {
			return &obj.BooleanValue{Value: args[0] == args[1]}, nil
		}
	}
	
	// For booleans
	if b1, ok1 := args[0].(*obj.BooleanValue); ok1 {
		if b2, ok2 := args[1].(*obj.BooleanValue); ok2 {
			return &obj.BooleanValue{Value: b1.Value == b2.Value}, nil
		}
	}
	
	// For integers
	if i1, ok1 := args[0].(*obj.IntegerValue); ok1 {
		if i2, ok2 := args[1].(*obj.IntegerValue); ok2 {
			return &obj.BooleanValue{Value: i1.Value == i2.Value}, nil
		}
	}
	
	// For rational numbers
	if r1, ok1 := args[0].(*obj.RationalValue); ok1 {
		if r2, ok2 := args[1].(*obj.RationalValue); ok2 {
			return &obj.BooleanValue{Value: r1.Numerator == r2.Numerator && r1.Denominator == r2.Denominator}, nil
		}
	}
	
	// For real numbers
	if r1, ok1 := args[0].(*obj.RealValue); ok1 {
		if r2, ok2 := args[1].(*obj.RealValue); ok2 {
			return &obj.BooleanValue{Value: r1.Value == r2.Value}, nil
		}
	}
	
	// For strings
	if s1, ok1 := args[0].(*obj.StringValue); ok1 {
		if s2, ok2 := args[1].(*obj.StringValue); ok2 {
			return &obj.BooleanValue{Value: s1.Value == s2.Value}, nil
		}
	}
	
	// For nil values
	if _, ok1 := args[0].(*obj.NilValue); ok1 {
		if _, ok2 := args[1].(*obj.NilValue); ok2 {
			return &obj.BooleanValue{Value: true}, nil
		}
	}
	
	// For other types, use eq? (same reference)
	return &obj.BooleanValue{Value: args[0] == args[1]}, nil
}

// builtinEqual implements the equal? function
// (equal? obj1 obj2) returns #t if obj1 and obj2 are structurally equal
func builtinEqual(args []obj.Value) (obj.Value, error) {
	if len(args) != 2 {
		return nil, fmt.Errorf("equal?: expected 2 arguments, got %d", len(args))
	}
	
	// Handle numeric comparisons across different types
	// First try to compare as numbers if possible
	switch n1 := args[0].(type) {
	case *obj.IntegerValue:
		switch n2 := args[1].(type) {
		case *obj.IntegerValue:
			return &obj.BooleanValue{Value: n1.Value == n2.Value}, nil
		case *obj.RationalValue:
			// Convert integer to rational and compare
			r1 := &obj.RationalValue{Numerator: n1.Value, Denominator: 1}
			// Cross multiply to avoid floating point inaccuracies
			return &obj.BooleanValue{Value: r1.Numerator*n2.Denominator == n2.Numerator*r1.Denominator}, nil
		case *obj.RealValue:
			// Convert integer to real and compare
			return &obj.BooleanValue{Value: float64(n1.Value) == n2.Value}, nil
		}
	case *obj.RationalValue:
		switch n2 := args[1].(type) {
		case *obj.IntegerValue:
			// Convert integer to rational and compare
			r2 := &obj.RationalValue{Numerator: n2.Value, Denominator: 1}
			// Cross multiply to avoid floating point inaccuracies
			return &obj.BooleanValue{Value: n1.Numerator*r2.Denominator == r2.Numerator*n1.Denominator}, nil
		case *obj.RationalValue:
			// Cross multiply to avoid floating point inaccuracies
			return &obj.BooleanValue{Value: n1.Numerator*n2.Denominator == n2.Numerator*n1.Denominator}, nil
		case *obj.RealValue:
			// Convert rational to real and compare
			r1Value := float64(n1.Numerator) / float64(n1.Denominator)
			return &obj.BooleanValue{Value: r1Value == n2.Value}, nil
		}
	case *obj.RealValue:
		switch n2 := args[1].(type) {
		case *obj.IntegerValue:
			// Convert integer to real and compare
			return &obj.BooleanValue{Value: n1.Value == float64(n2.Value)}, nil
		case *obj.RationalValue:
			// Convert rational to real and compare
			r2Value := float64(n2.Numerator) / float64(n2.Denominator)
			return &obj.BooleanValue{Value: n1.Value == r2Value}, nil
		case *obj.RealValue:
			return &obj.BooleanValue{Value: n1.Value == n2.Value}, nil
		}
	}
	
	// Use eqv? for non-numeric, non-pair values
	result, err := builtinEqv(args)
	if err != nil {
		return nil, err
	}
	if boolValue, ok := result.(*obj.BooleanValue); ok && boolValue.Value {
		return result, nil
	}
	
	// For pairs, check car and cdr recursively
	if p1, ok1 := args[0].(*obj.PairValue); ok1 {
		if p2, ok2 := args[1].(*obj.PairValue); ok2 {
			// Check car
			carEqual, err := builtinEqual([]obj.Value{p1.Car, p2.Car})
			if err != nil {
				return nil, err
			}
			if carBool, ok := carEqual.(*obj.BooleanValue); !ok || !carBool.Value {
				return &obj.BooleanValue{Value: false}, nil
			}
			
			// Check cdr
			cdrEqual, err := builtinEqual([]obj.Value{p1.Cdr, p2.Cdr})
			if err != nil {
				return nil, err
			}
			if cdrBool, ok := cdrEqual.(*obj.BooleanValue); !ok || !cdrBool.Value {
				return &obj.BooleanValue{Value: false}, nil
			}
			
			// Both car and cdr are equal
			return &obj.BooleanValue{Value: true}, nil
		}
	}
	
	// Values are not equal
	return &obj.BooleanValue{Value: false}, nil
}

// builtinNumerator implements the numerator function
// (numerator number) returns the numerator of the number
func builtinNumerator(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("numerator: expected 1 argument, got %d", len(args))
	}

	switch n := args[0].(type) {
	case *obj.IntegerValue:
		// For integers, numerator is the integer itself
		return n, nil
	case *obj.RationalValue:
		// For rational numbers, return the numerator
		return &obj.IntegerValue{Value: n.Numerator}, nil
	case *obj.RealValue:
		// For real numbers, we can't get an exact numerator
		return nil, fmt.Errorf("numerator: cannot get numerator of real number")
	default:
		return nil, fmt.Errorf("numerator: expected numeric argument, got %s", args[0].Type())
	}
}

// builtinDenominator implements the denominator function
// (denominator number) returns the denominator of the number
func builtinDenominator(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("denominator: expected 1 argument, got %d", len(args))
	}

	switch n := args[0].(type) {
	case *obj.IntegerValue:
		// For integers, denominator is 1
		return &obj.IntegerValue{Value: 1}, nil
	case *obj.RationalValue:
		// For rational numbers, return the denominator
		return &obj.IntegerValue{Value: n.Denominator}, nil
	case *obj.RealValue:
		// For real numbers, we can't get an exact denominator
		return nil, fmt.Errorf("denominator: cannot get denominator of real number")
	default:
		return nil, fmt.Errorf("denominator: expected numeric argument, got %s", args[0].Type())
	}
}

// builtinExactToInexact implements the exact->inexact function
// (exact->inexact number) converts an exact number to an inexact number
func builtinExactToInexact(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("exact->inexact: expected 1 argument, got %d", len(args))
	}

	switch n := args[0].(type) {
	case *obj.IntegerValue:
		// Convert integer to real
		return &obj.RealValue{Value: float64(n.Value)}, nil
	case *obj.RationalValue:
		// Convert rational number to real
		return &obj.RealValue{Value: float64(n.Numerator) / float64(n.Denominator)}, nil
	case *obj.RealValue:
		// Already inexact, return as is
		return n, nil
	default:
		return nil, fmt.Errorf("exact->inexact: expected numeric argument, got %s", args[0].Type())
	}
}

// builtinInexactToExact implements the inexact->exact function
// (inexact->exact number) converts an inexact number to an exact number
func builtinInexactToExact(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("inexact->exact: expected 1 argument, got %d", len(args))
	}

	switch n := args[0].(type) {
	case *obj.IntegerValue:
		// Already exact, return as is
		return n, nil
	case *obj.RationalValue:
		// Already exact, return as is
		return n, nil
	case *obj.RealValue:
		// For simplicity, we'll convert real to integer if it's a whole number
		// Otherwise, we'll return an error since we can't represent arbitrary reals as fractions
		if float64(int64(n.Value)) == n.Value {
			return &obj.IntegerValue{Value: int64(n.Value)}, nil
		} else {
			return nil, fmt.Errorf("inexact->exact: cannot convert %g to exact form", n.Value)
		}
	default:
		return nil, fmt.Errorf("inexact->exact: expected numeric argument, got %s", args[0].Type())
	}
}

// builtinFloor implements the floor function
// (floor number) returns the largest integer less than or equal to the number
func builtinFloor(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("floor: expected 1 argument, got %d", len(args))
	}

	switch n := args[0].(type) {
	case *obj.IntegerValue:
		// Already an integer
		return n, nil
	case *obj.RationalValue:
		// For rational numbers, compute floor by dividing and truncating
		// Handle positive and negative values correctly
		value := float64(n.Numerator) / float64(n.Denominator)
		floorValue := int64(value)
		if value < 0 && float64(floorValue) != value {
			floorValue--
		}
		return &obj.IntegerValue{Value: floorValue}, nil
	case *obj.RealValue:
		// For real numbers, use math.Floor
		return &obj.IntegerValue{Value: int64(math.Floor(n.Value))}, nil
	default:
		return nil, fmt.Errorf("floor: expected numeric argument, got %s", args[0].Type())
	}
}

// builtinCeiling implements the ceiling function
// (ceiling number) returns the smallest integer greater than or equal to the number
func builtinCeiling(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("ceiling: expected 1 argument, got %d", len(args))
	}

	switch n := args[0].(type) {
	case *obj.IntegerValue:
		// Already an integer
		return n, nil
	case *obj.RationalValue:
		// For rational numbers, compute ceiling by dividing and rounding up
		// Handle positive and negative values correctly
		value := float64(n.Numerator) / float64(n.Denominator)
		ceilingValue := int64(value)
		if value > 0 && float64(ceilingValue) != value {
			ceilingValue++
		}
		return &obj.IntegerValue{Value: ceilingValue}, nil
	case *obj.RealValue:
		// For real numbers, use math.Ceil
		return &obj.IntegerValue{Value: int64(math.Ceil(n.Value))}, nil
	default:
		return nil, fmt.Errorf("ceiling: expected numeric argument, got %s", args[0].Type())
	}
}

// builtinTruncate implements the truncate function
// (truncate number) returns the integer closest to the number whose absolute value is not larger
func builtinTruncate(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("truncate: expected 1 argument, got %d", len(args))
	}

	switch n := args[0].(type) {
	case *obj.IntegerValue:
		// Already an integer
		return n, nil
	case *obj.RationalValue:
		// For rational numbers, truncate towards zero
		value := float64(n.Numerator) / float64(n.Denominator)
		truncatedValue := int64(value)
		return &obj.IntegerValue{Value: truncatedValue}, nil
	case *obj.RealValue:
		// For real numbers, truncate towards zero
		if n.Value >= 0 {
			return &obj.IntegerValue{Value: int64(math.Floor(n.Value))}, nil
		} else {
			return &obj.IntegerValue{Value: int64(math.Ceil(n.Value))}, nil
		}
	default:
		return nil, fmt.Errorf("truncate: expected numeric argument, got %s", args[0].Type())
	}
}

// builtinRound implements the round function
// (round number) returns the closest integer to the number
func builtinRound(args []obj.Value) (obj.Value, error) {
	if len(args) != 1 {
		return nil, fmt.Errorf("round: expected 1 argument, got %d", len(args))
	}

	switch n := args[0].(type) {
	case *obj.IntegerValue:
		// Already an integer
		return n, nil
	case *obj.RationalValue:
		// For rational numbers, round to nearest integer
		value := float64(n.Numerator) / float64(n.Denominator)
		roundedValue := int64(value + 0.5)
		if value < 0 && value != float64(int64(value)) {
			roundedValue--
		}
		return &obj.IntegerValue{Value: roundedValue}, nil
	case *obj.RealValue:
		// For real numbers, use math.Round
		return &obj.IntegerValue{Value: int64(math.Round(n.Value))}, nil
	default:
		return nil, fmt.Errorf("round: expected numeric argument, got %s", args[0].Type())
	}
}